﻿// Accord Imaging Library
// The Accord.NET Framework
// http://accord-framework.net
//
// Copyright © César Souza, 2009-2017
// cesarsouza at gmail.com
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

namespace Accord.Imaging
{
    using Accord.Compat;
    using Accord.MachineLearning;
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Linq;

    /// <summary>
    ///   Base class for image feature extractors that implement the <see cref="IImageFeatureExtractor{TPoint}"/> interface.
    /// </summary>
    /// 
    /// <typeparam name="TFeature">The type of the extracted features (e.g. <see cref="SpeededUpRobustFeaturePoint"/>, <see cref="FastRetinaKeypoint"/>]).</typeparam>
    /// 
    /// <seealso cref="Accord.Imaging.IImageFeatureExtractor{TPoint}" />
    /// <seealso cref="BaseSparseFeatureExtractor{T}"/>
    /// 
    [Serializable]
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
    public abstract class BaseFeatureExtractor<TFeature> : IImageFeatureExtractor<TFeature>, IDisposable, IFeatureDetector<TFeature>, ICloneable
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore CS0612 // Type or member is obsolete
    {
        /// <summary>
        ///   Gets the list of image pixel formats that are supported by 
        ///   this extractor. The extractor will check whether the pixel
        ///   format of any provided images are in this list to determine
        ///   whether the image can be processed or not.
        /// </summary>
        /// 
        public ISet<PixelFormat> SupportedFormats { get; protected set; }

        /// <summary>
        ///   Returns -1.
        /// </summary>
        /// 
        public int NumberOfInputs
        {
            get { return -1; }
        }

        /// <summary>
        ///   Gets the dimensionality of the features generated by this extractor.
        /// </summary>
        /// 
        public int NumberOfOutputs
        {
            get; protected set;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="BaseFeatureExtractor{TPoint}"/> class.
        /// </summary>
        public BaseFeatureExtractor()
        {
            this.SupportedFormats = new HashSet<PixelFormat>();
        }

        /// <summary>
        ///   Obsolete. Please use the <see cref="Transform(Bitmap)"/> method instead.
        /// </summary>
        /// 
        [Obsolete("Please use the Transform(image) method instead.")]
        public List<double[]> ProcessImage(Bitmap image)
        {
            var t = (IEnumerable<IFeatureDescriptor<double[]>>)Transform(image);
            return new List<double[]>(t.Select(x => x.Descriptor));
        }

        /// <summary>
        ///   Obsolete. Please use the <see cref="Transform(Bitmap)"/> method instead.
        /// </summary>
        /// 
        [Obsolete("Please use the Transform(image) method instead.")]
        public List<double[]> ProcessImage(BitmapData imageData)
        {
            var t = (IEnumerable<IFeatureDescriptor<double[]>>)Transform(new UnmanagedImage(imageData));
            return new List<double[]>(t.Select(x => x.Descriptor));
        }

        /// <summary>
        ///   Obsolete. Please use the <see cref="Transform(Bitmap)"/> method instead.
        /// </summary>
        /// 
        [Obsolete("Please use the Transform(image) method instead.")]
        public List<double[]> ProcessImage(UnmanagedImage image)
        {
            var t = (IEnumerable<IFeatureDescriptor<double[]>>)Transform(image);
            return new List<double[]>(t.Select(x => x.Descriptor));
        }

        IEnumerable<TFeature> IFeatureDetector<TFeature>.ProcessImage(Bitmap image)
        {
            return Transform(image);
        }

        IEnumerable<TFeature> IFeatureDetector<TFeature>.ProcessImage(BitmapData imageData)
        {
            return Transform(new UnmanagedImage(imageData));
        }

        IEnumerable<TFeature> IFeatureDetector<TFeature>.ProcessImage(UnmanagedImage image)
        {
            return Transform(image);
        }

        /// <summary>
        ///   Applies the transformation to an input, producing an associated output.
        /// </summary>
        /// 
        /// <param name="input">The input data to which the transformation should be applied.</param>
        /// 
        /// <returns>The output generated by applying this transformation to the given input.</returns>
        /// 
        public IEnumerable<TFeature> Transform(Bitmap input)
        {
            return input.LockBits(ImageLockMode.ReadOnly, (bmp) => Transform(bmp));
        }

        /// <summary>
        ///   Applies the transformation to an input, producing an associated output.
        /// </summary>
        /// 
        /// <param name="input">The input data to which the transformation should be applied.</param>
        /// 
        /// <returns>The output generated by applying this transformation to the given input.</returns>
        /// 
        public IEnumerable<TFeature>[] Transform(Bitmap[] input)
        {
            return Transform(input, new IList<TFeature>[input.Length]);
        }

        /// <summary>
        ///   Applies the transformation to a set of input vectors,
        ///   producing an associated set of output vectors.
        /// </summary>
        /// 
        /// <param name="input">The input data to which
        ///   the transformation should be applied.</param>
        /// <param name="result">The location to where to store the
        ///   result of this transformation.</param>
        /// 
        /// <returns>The output generated by applying this
        ///   transformation to the given input.</returns>
        /// 
        public IEnumerable<TFeature>[] Transform(Bitmap[] input, IEnumerable<TFeature>[] result)
        {
            for (int i = 0; i < input.Length; i++)
                result[i] = Transform(input[i]);
            return result;
        }

        /// <summary>
        ///   Applies the transformation to an input, producing an associated output.
        /// </summary>
        /// 
        /// <param name="input">The input data to which the transformation should be applied.</param>
        /// 
        /// <returns>The output generated by applying this transformation to the given input.</returns>
        /// 
        public IEnumerable<TFeature> Transform(UnmanagedImage input)
        {
            // check image format
            if (!SupportedFormats.Contains(input.PixelFormat))
                throw new UnsupportedImageFormatException("Unsupported pixel format of the source image.");

            return InnerTransform(input);
        }

        /// <summary>
        ///   Applies the transformation to an input, producing an associated output.
        /// </summary>
        /// 
        /// <param name="input">The input data to which the transformation should be applied.</param>
        /// 
        /// <returns>The output generated by applying this transformation to the given input.</returns>
        /// 
        public IEnumerable<TFeature>[] Transform(UnmanagedImage[] input)
        {
            return Transform(input, new IList<TFeature>[input.Length]);
        }

        /// <summary>
        ///   Applies the transformation to a set of input vectors,
        ///   producing an associated set of output vectors.
        /// </summary>
        /// 
        /// <param name="input">The input data to which
        ///   the transformation should be applied.</param>
        /// <param name="result">The location to where to store the
        ///   result of this transformation.</param>
        /// 
        /// <returns>The output generated by applying this
        ///   transformation to the given input.</returns>
        /// 
        public IEnumerable<TFeature>[] Transform(UnmanagedImage[] input, IEnumerable<TFeature>[] result)
        {
            for (int i = 0; i < input.Length; i++)
                result[i] = Transform(input[i]);
            return result;
        }

        /// <summary>
        ///   This method should be implemented by inheriting classes to implement the 
        ///   actual feature extraction, transforming the input image into a list of features.
        /// </summary>
        /// 
        protected abstract IEnumerable<TFeature> InnerTransform(UnmanagedImage input);


        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// 
        public object Clone()
        {
            return Clone(SupportedFormats);
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// 
        protected abstract object Clone(ISet<PixelFormat> supportedFormats);


        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources.
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects).
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~BaseFeatureDetector() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion


        int ITransform.NumberOfInputs
        {
            get { return NumberOfInputs; }
            set { throw new InvalidOperationException("This property is read-only."); }
        }

        int ITransform.NumberOfOutputs
        {
            get { return NumberOfOutputs; }
            set { throw new InvalidOperationException("This property is read-only."); }
        }

    }
}